1 /**
2 User-defined attributes that can be used with the KeyedItem mixin.
3 The constraints module contains:
4   $(TOC UniqueConstraintColumn)
5   $(TOC PrimaryKeyColumn)
6   $(TOC ExclusionConstraint)
7   $(TOC CheckConstraint)
8   $(TOC NotNull)
9   $(TOC SetConstraint)
10   $(TOC EnumConstraint)
11   $(TOC Rule)
12   $(TOC ForeignKey)
13   $(TOC ForeignKeyConstraint)
14   $(TOC Default)
15 
16 License: $(GPL2)
17 
18 Authors: Matthew Armbruster
19 
20 $(B Source:) $(SRC $(SRCFILENAME))
21 Copyright: 2016
22  */
23 module db_constraints.constraints;
24 
25 import std.functional : binaryFun, unaryFun;
26 
27 /**
28 KeyedItem will create a struct with $(I name) defined in the compile-time
29 argument. For example a property marked with @UniqueColumn!("uc_Person") will
30 be part of the struct uc_Person.
31 Params:
32     name_ = The name of the constraint which is the structs name
33  */
34 struct UniqueConstraintColumn(string name_)
35 {
36     enum name = name_;
37 }
38 
39 /**
40 An alias for the primary key column. A member with this attribute
41 must also have the NotNull attribute.
42  */
43 alias PrimaryKeyColumn = UniqueConstraintColumn!("PrimaryKey");
44 
45 /**
46 Mimics Postgresql's Exclude Constraint. This will exclude any
47 items that return true to the $(D exclusion_).
48 
49 Version: \>= 0.0.7
50  */
51 struct ExclusionConstraint(alias exclusion_, string name_ = "")
52     if (is(typeof(binaryFun!exclusion_)))
53 {
54     alias exclusion = binaryFun!exclusion_;
55     enum name = name_;
56 }
57 
58 /**
59 $(WIKI keyeditem, KeyedItem.checkConstraints) will check all of the members
60 marked with this attribute and use the check given.
61 
62 Version: \>= 0.0.6 allows you to mark your class as well.
63 
64 Params:
65     check_ = The function that returns a boolean
66     name_ = Name used in the error message if the function returns false
67  */
68 struct CheckConstraint(alias check_, string name_ = "")
69     if (is(typeof(unaryFun!check_)))
70 {
71     alias check = unaryFun!check_;
72     enum name = name_;
73 }
74 
75 /**
76 Alias for a special check constraint that makes sure the column is never null.
77 This is checked the same time as all the other check constraints. The name of
78 the constraint is NotNull in the error messages if this is ever violated.
79  */
80 alias NotNull = CheckConstraint!(
81     function bool(auto ref a)
82     {
83         static if (__traits(hasMember, typeof(a), "isNull"))
84         {
85             return !a.isNull;
86         }
87         else static if (__traits(compiles, typeof(a).init == null))
88         {
89             return a !is null;
90         }
91         else
92         {
93             return true;
94         }
95     }, "NotNull");
96 
97 import std.traits : isExpressions;
98 /**
99 Alias for check constraint that makes sure the property that
100 has this attribute only contains members in the set. This should
101 act like the SET constraint in MySQL. It will sort and remove the
102 duplicates of the SET. This does modify the value coming in. This
103 is only for strings.
104 
105 If $(D isStrict) is true, SetConstraint will return false if
106 you include a value not in the set. If $(D isStrict) is
107 false, the value will be set to an empty string.
108 
109 Version: \>= 0.0.6 for $(D isStrict) option.
110 \>= 0.0.4 is always strict.
111  */
112 template SetConstraint(values...)
113     if (isExpressions!values)
114 {
115     alias SetConstraint = SetConstraint!(true, values);
116 }
117 /// ditto
118 template SetConstraint(bool isStrict, values...)
119     if (isExpressions!values)
120 {
121     alias SetConstraint = CheckConstraint!(
122         function bool(auto ref a)
123         {
124             static assert(is(typeof(a) == string));
125 
126             if (a !is null)
127             {
128                 import std.array : split;
129                 import std.algorithm : among, aSort = sort, uniq;
130                 auto options = a.split(",").aSort.uniq;
131                 a = "";
132                 foreach(string option; options)
133                 {
134                     if (!option.among!(values))
135                     {
136                         static if (isStrict)
137                         {
138                             return false;
139                         }
140                         else
141                         {
142                             continue;
143                         }
144                     }
145                     if (a != "")
146                     {
147                         a ~= ",";
148                     }
149                     a ~= option;
150                 }
151             }
152             return true;
153         }, "Set");
154 }
155 
156 /**
157 Alias for check constraint that makes sure the property that
158 has this attribute only contains a member that is part of the
159 enumeration. This should act like the ENUM constraint in MySQL.
160 This does modify the value coming in. This is only for strings.
161 
162 If $(D isStrict) is true, EnumConstraint will return false if
163 you include a value not in the enumeration. If $(D isStrict) is
164 false, the value will be set to an empty string.
165 
166 Version: \>= 0.0.6
167  */
168 template EnumConstraint(values...)
169     if (isExpressions!values)
170 {
171     alias EnumConstraint = EnumConstraint!(true, values);
172 }
173 /// ditto
174 template EnumConstraint(bool isStrict, values...)
175     if (isExpressions!values)
176 {
177     alias EnumConstraint = CheckConstraint!(
178         function bool(auto ref a)
179         {
180             import std.algorithm : among;
181             static assert(is(typeof(a) == string));
182 
183             if (a !is null)
184             {
185                 static if (isStrict)
186                 {
187                     return a.among!(values);
188                 }
189                 else
190                 {
191                     if (!a.among!(values))
192                     {
193                         a = "";
194                     }
195                 }
196             }
197             return true;
198         }, "Enum");
199 }
200 
201 /**
202 Rules for foreign keys when updating or deleting.
203  */
204 enum Rule
205 {
206 /**
207 When a parent key is modified or deleted from the collection, no special
208 action is taken. If you are using MySQL or MSSQL use
209 $(SRCTAG Rule.restrict) instead for the desired effect.
210  */
211     noAction,
212 /**
213 The item is prohibited from deleting or modifying a parent key when there exists
214 one or more child keys mapped to it. This is the default.
215 
216 $(THROWS ForeignKeyException, when a member changes.)
217  */
218     restrict,
219 /**
220 Sets the member to $(D null) when deleting or modifying a parent key.
221 
222 $(THROWS ForeignKeyException, when the type cannot be set to null.)
223  */
224     setNull,
225 /**
226 Sets the member to the Default value when deleting or modifying a parent key.
227 If there is no defined Default then the member is set to its types initial
228 value.
229  */
230     setDefault,
231 /**
232 Updates or deletes the item based on what happened to the parent key.
233  */
234     cascade
235 }
236 
237 /**
238 $(SRCTAG ForeignKeyConstraint) should be used instead of this struct.
239 This is more the behind the scenes struct.
240 Params:
241     name_ = The name of the foreign key constraint. Will be used in error message when violated
242     columnNames_ = The members in the child class that are used in the foreign key
243     referencedTableName_ = The referenced table's name (collection class)
244     referencedColumnNames_ = The members in the parent class that are references in the foreign key
245     updateRule_ = Rule when a foreign key is updated that is being referenced
246     deleteRule_ = Rule when a foreign key is deleted that is being referenced
247  */
248 struct ForeignKey(string name_,
249                   string[] columnNames_,
250                   string referencedTableName_,
251                   string[] referencedColumnNames_,
252                   Rule updateRule_,
253                   Rule deleteRule_)
254 {
255     enum string name = name_;
256     enum string[] columnNames = columnNames_;
257     enum string referencedTableName = referencedTableName_;
258     enum string[] referencedColumnNames = referencedColumnNames_;
259     enum Rule updateRule = updateRule_;
260     enum Rule deleteRule = deleteRule_;
261 }
262 
263 /**
264 The foreign key user-defined attribute.
265  */
266 template ForeignKeyConstraint(string name_, string[] columnNames_,
267                               string referencedTableName_,
268                               string[] referencedColumnNames_,
269                               Rule updateRule_, Rule deleteRule_)
270 {
271     alias ForeignKeyConstraint = ForeignKey!(name_, columnNames_,
272                                              referencedTableName_,
273                                              referencedColumnNames_,
274                                              updateRule_, deleteRule_);
275 }
276 
277 /// ditto
278 template ForeignKeyConstraint(string name_, string[] columnNames_,
279                               string referencedTableName_,
280                               string[] referencedColumnNames_)
281 {
282     alias ForeignKeyConstraint = ForeignKey!(name_, columnNames_,
283                                              referencedTableName_,
284                                              referencedColumnNames_,
285                                              Rule.restrict, Rule.restrict);
286 }
287 
288 /// ditto
289 template ForeignKeyConstraint(string[] columnNames_,
290                               string referencedTableName_,
291                               string[] referencedColumnNames_,
292                               Rule updateRule_, Rule deleteRule_)
293 {
294     alias ForeignKeyConstraint = ForeignKey!("", columnNames_,
295                                              referencedTableName_,
296                                              referencedColumnNames_,
297                                              updateRule_, deleteRule_);
298 }
299 
300 /// ditto
301 template ForeignKeyConstraint(string[] columnNames_,
302                               string referencedTableName_,
303                               string[] referencedColumnNames_)
304 {
305     alias ForeignKeyConstraint = ForeignKey!("", columnNames_,
306                                              referencedTableName_,
307                                              referencedColumnNames_,
308                                              Rule.restrict, Rule.restrict);
309 }
310 
311 /**
312 Default used with $(SRCTAG Rule.setDefault) for foreign keys.
313 Params:
314     value_ = the value that should be used for the default value.
315  */
316 struct Default(alias value_)
317 {
318     enum value = value_;
319 }
320 
321